Header file tagged_union.hpp

namespace type_safe
{
    template <typename T>
    struct union_type;
    
    template <typename ... Ts>
    struct union_types;
    
    template <typename ... Types>
    class tagged_union;
    
    template <typename ... Types, typename Func, typename ... Args>
    void with(tagged_union<Types...>& u, Func&& f, Args&&... additional_args);
    template <typename ... Types, typename Func, typename ... Args>
    void with(const tagged_union<Types...>& u, Func&& f, Args&&... additional_args);
    template <typename ... Types, typename Func, typename ... Args>
    void with(tagged_union<Types...>&& u, Func&& f, Args&&... additional_args);
    template <typename ... Types, typename Func, typename ... Args>
    void with(const tagged_union<Types...>&& u, Func&& f, Args&&... additional_args);
    
    template <typename ... Types>
    void destroy(tagged_union<Types...>& u) noexcept;
    
    template <typename ... Types>
    void copy(tagged_union<Types...>& dest, const tagged_union<Types...>& org);
    template <typename ... Types>
    void move(tagged_union<Types...>& dest, tagged_union<Types...>&& org);
}

Class template type_safe::union_type [variant]

template <typename T>
struct union_type
{
    constexpr union_type();
};

Tag type so no explicit template instantiation of function parameters is required.

Class template type_safe::union_types [variant]

template <typename ... Ts>
struct union_types
{
};

Very basic typelist.

Class template type_safe::tagged_union [variant]

template <typename ... Types>
class tagged_union
{
public:
    using types = union_types<typename std::decay<Types>::type...>;
    
    class type_id;
    
    static constexpr type_id invalid_type = type_id();
    
    tagged_union() noexcept = default;
    
    ~tagged_union() noexcept = default;
    
    tagged_union(const tagged_union&) = delete;
    
    tagged_union& operator=(const tagged_union&) = delete;
    
    template <typename T, typename ... Args>
    void emplace(union_type<T>, Args&&... args);
    
    template <typename T>
    void destroy(union_type<T> type) noexcept;
    
    const type_id& type() const noexcept;
    
    bool has_value() const noexcept;
    
    template <typename T>
    T& value(union_type<T> type) & noexcept;
    template <typename T>
    const T& value(union_type<T> type) const & noexcept;
    template <typename T>
    T&& value(union_type<T> type) && noexcept;
    template <typename T>
    const T&& value(union_type<T> type) const && noexcept;
};

A tagged union.

It is much like a plain old C union, but remembers which type it currently stores. It can either store one of the given types or no type at all.

Notes: Like the C union it does not automatically destroy the currently stored type, and copy operations are deleted.

Class type_safe::tagged_union::type_id

class type_id
: public strong_typedef<class type_safe::tagged_union::type_id, std::size_t>,
  public strong_typedef_op::equality_comparison<type_id>,
  public strong_typedef_op::relational_comparison<type_id>
{
public:
    template <typename T>
    static constexpr bool is_valid();
    
    constexpr type_id() noexcept;
    
    template <typename T>
    constexpr type_id(union_type<T>) noexcept;
    
    operator bool() const noexcept;
};

The id of a type.

It is a ts::strong_typedef for std::size_t and provides equality and relational comparison.

Function template type_safe::tagged_union::type_id::is_valid

template <typename T>
static constexpr bool is_valid();

Returns: true if T is a valid type, false otherwise.

Default constructor type_safe::tagged_union::type_id::type_id

constexpr type_id() noexcept;

Effects: Initializes it to an invalid value.

Notes: The invalid value compares less than all valid values.

Function template type_safe::tagged_union::type_id::type_id

template <typename T>
constexpr type_id(union_type<T>) noexcept;

Effects: Initializes it to the value of the type T. If T is not one of the types of the union types, it will be the same as the default constructor.

Conversion operator type_safe::tagged_union::type_id::operator bool

operator bool() const noexcept;

Returns: true if the id is valid, false otherwise.


Variable type_safe::tagged_union::invalid_type

static constexpr type_id invalid_type = type_id();

A global invalid type id object.

Destructor type_safe::tagged_union::~tagged_union

~tagged_union() noexcept = default;

Notes: Does not destroy the currently stored type.

Function template type_safe::tagged_union::emplace

template <typename T, typename ... Args>
void emplace(union_type<T>, Args&&... args);

Effects: Creates a new object of given type by perfectly forwarding args.

Throws: Anything thrown by Ts constructor, in which case the union will stay empty.

Requires: The union must currently be empty. and T must be a valid type and constructible from the arguments.

Function template type_safe::tagged_union::destroy

template <typename T>
void destroy(union_type<T> type) noexcept;

Effects: Destroys the currently stored type by calling its destructor, and setting the union to the empty state.

Requires: The union must currently store an object of the given type.

Function type_safe::tagged_union::type

const type_id& type() const noexcept;

Returns: The type_id of the type currently stored, or invalid_type if there is none.

Function type_safe::tagged_union::has_value

bool has_value() const noexcept;

Returns: true if there is a type stored, false otherwise.

Function template type_safe::tagged_union::value

(1)  template <typename T>
     T& value(union_type<T> type) & noexcept;

(2)  template <typename T>
     const T& value(union_type<T> type) const & noexcept;

(3)  template <typename T>
     T&& value(union_type<T> type) && noexcept;

(4)  template <typename T>
     const T&& value(union_type<T> type) const && noexcept;

Returns: A (const) lvalue/rvalue reference to the currently stored type.

Requires: The union must currently store an object of the given type.


Function template type_safe::with [variant]

(1)  template <typename ... Types, typename Func, typename ... Args>
     void with(tagged_union<Types...>& u, Func&& f, Args&&... additional_args);

(2)  template <typename ... Types, typename Func, typename ... Args>
     void with(const tagged_union<Types...>& u, Func&& f, Args&&... additional_args);

(3)  template <typename ... Types, typename Func, typename ... Args>
     void with(tagged_union<Types...>&& u, Func&& f, Args&&... additional_args);

(4)  template <typename ... Types, typename Func, typename ... Args>
     void with(const tagged_union<Types...>&& u, Func&& f, Args&&... additional_args);

Effects: If the union is empty, does nothing. Otherwise let the union contain an object of type T. If the functor is callable for the T, calls its operator() passing it the stored object. Else does nothing.

Function template type_safe::destroy [variant]

template <typename ... Types>
void destroy(tagged_union<Types...>& u) noexcept;

Effects: Destroys the type currently stored in the ts::tagged_union, by calling u.destroy(union_type<T>{}).

Function template type_safe::copy [variant]

(1)  template <typename ... Types>
     void copy(tagged_union<Types...>& dest, const tagged_union<Types...>& org);

(2)  template <typename ... Types>
     void move(tagged_union<Types...>& dest, tagged_union<Types...>&& org);

Effects: Copies the type currently stored in one ts::tagged_union to another. This is equivalent to calling dest.emplace(union_type<T>{}, org.value(union_type<T>{})) (1)

dest.emplace(union_type<T>{}, std::move(org).value(union_type<T>{})) (2), where T is the type currently stored in the union.

Throws: Anything by the copy/move constructor in which case nothing has changed.

Requires: dest must not store a type, and all types must be copyable/moveable.